﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Networking;
using CorruptedSmileStudio.MessageBox;

public class NetworkDungeonController : MonoBehaviour
{

    #region Public controls

    public bool gameRunning = false;

    public string joinIpAddress = "127.0.0.1";
    public string playerName = "Unnamed";

    public int maxPlayers = 4;
    public int port = 25053;

    public GameObject playerController = null;
    public GameObject monster = null;

    public bool gameOver = false;

    public int playerCount = 1;
    public bool singlePlayer = true;

    private string gameOverMessage = string.Empty;
    private bool gameOverShown = false;

    #endregion

    #region Private member data

    private NetworkDungeon dungeon = null;
    byte[] serializedDungeon;
    List<GameObject> sceneObjects = new List<GameObject>();

    private int dungeonSize = 64;
    private int pieceSize = 10;

    private bool isServer = false;
    private NetworkPlayerManager manager;
    private NetworkPlayer[] networkPlayers;
    private int playerNumber = -1;

    bool showConnectErrorMessage = false;
    string connectErrorMessage = string.Empty;
    public bool skeletonSpawned = false;

    #endregion

    #region Unity Common Events

    // Use this for initialization
	void Start () {
        print("Here I am!");
        Debug.Log("Or here, maybe?");
        manager = new NetworkPlayerManager(maxPlayers);
 
        DontDestroyOnLoad(this.gameObject);
	}
	
	// Update is called once per frame
	void Update () {

        if (skeletonSpawned && gameRunning && GameObject.FindGameObjectsWithTag("Skeleton").Length == 0)
        {
            // Victory!
            // We'll get this update everywhere.
            gameOver = true;
            // TODO: Task 7 - Show a "Yay!" message on victory
            // TODO: Show a victory message on the screen
            //print("Victory detected");
            gameOverMessage = "All skeletons dead. You win!";
            return;

        }

        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        bool anyPlayerAlive = false;
        foreach (GameObject player in players)
        {
            if (player.GetComponent<CombatData>().currentHealth > 0)
            {
                anyPlayerAlive = true;
            }
        }

        if (gameRunning && !anyPlayerAlive)
        {
            gameOver = true;
            // TODO: Show a loss message on the screen
            //print("Annihilation detected");
            gameOverMessage = "All players dead. You lose.";
        }

        // TODO: Task 8 - transition everybody back to the main menu on victory (or defeat)
        // Also self-explanatory; make sure to clean objects. 

	}

    void ReturnToMainMenu()
    {
        foreach (GameObject gameObject in sceneObjects)
        {
            GameObject.Destroy(gameObject);
        }
        Application.LoadLevel(0);
        Destroy(this.gameObject);
    }

    void OnGUI()
    {
        if (!gameRunning)
        {
            if (GUI.Button(new Rect(5, 5, 160, 20), "Start Single Player"))
            {
                singlePlayer = true;
                Preserver.Instance.SetValue("chatEnabled", false);
                Preserver.Instance.SetValue("playerName", playerName);
                GenerateDungeon(true);
                LoadDungeonObjects();

                foreach (Vector3 monsterSpawnPoint in dungeon.monsterSpawnPointList)
                {
                    Vector3 monsterLocation = SpawnPointToGameLocation(monsterSpawnPoint);
                    monsterLocation.y = 0;
                    GameObject instantiatedMonster = Instantiate(monster, monsterLocation, Quaternion.identity) as GameObject;
                    sceneObjects.Add(instantiatedMonster);
                    DontDestroyOnLoad(instantiatedMonster);
                }

                isServer = false;
                playerNumber = 0;
                Vector3 location = SpawnPointToGameLocation(dungeon.spawnPointList.spawnPoints[playerNumber]);
                GameObject instantiatedPlayer = Instantiate(playerController, location, Quaternion.identity) as GameObject;
                sceneObjects.Add(instantiatedPlayer);
                DontDestroyOnLoad(instantiatedPlayer);
                Application.LoadLevel("networkingGame");
            }

            if (GUI.Button(new Rect(5, 60, 160, 20), "Start Multiplayer"))
            {
                singlePlayer = false;
                Preserver.Instance.SetValue("chatEnabled", true);
                Preserver.Instance.SetValue("playerName", playerName);
                GenerateDungeon(true);
                LoadDungeonObjects();

                serializedDungeon = dungeon.Serialize();

                StringBuilder sb = new StringBuilder();
                foreach (byte b in serializedDungeon) sb.AppendFormat("{0} ", b);
                print(sb.ToString());

                Network.InitializeServer(maxPlayers, port, false);
                isServer = true;
                playerNumber = manager.AddPlayer(Network.player);
                Vector3 location = SpawnPointToGameLocation(dungeon.spawnPointList.spawnPoints[playerNumber]);
                Network.Instantiate(playerController, location, Quaternion.identity, 0);


                foreach (Vector3 monsterSpawnPoint in dungeon.monsterSpawnPointList)
                {
                    Vector3 monsterLocation = SpawnPointToGameLocation(monsterSpawnPoint);
                    monsterLocation.y = 0;
                    GameObject instantiatedMonster = Network.Instantiate(monster, monsterLocation, Quaternion.identity, 0) as GameObject;
                    sceneObjects.Add(instantiatedMonster);
                    DontDestroyOnLoad(instantiatedMonster);
                }

                Application.LoadLevel("networkingGame");
            }

            GUI.Label(new Rect(5, 90, 110, 20), "Join Multiplayer");
            joinIpAddress = GUI.TextField(new Rect(120, 90, 100, 20), joinIpAddress);
            if (GUI.Button(new Rect(230, 90, 60, 20), "Join"))
            {
                singlePlayer = false;
                showConnectErrorMessage = false;
                connectErrorMessage = string.Empty;
                Preserver.Instance.SetValue("playerName", playerName);
                try
                {
                    NetworkConnectionError error = Network.Connect(joinIpAddress, port);
                    if (error != NetworkConnectionError.NoError)
                    {
                        HumanizeNetworkConnectionError(error);
                        showConnectErrorMessage = true;
                    }
                }
                catch 
                {
                    print("failure to connect");
                }
            }
            if (showConnectErrorMessage)
            {
                GUI.color = Color.red;
                GUI.Label(new Rect(300, 90, 500, 20), connectErrorMessage);
                GUI.color = Color.white;
            }

            GUI.Label(new Rect(5, 120, 80, 20), "Username");
            playerName = GUI.TextField(new Rect(90, 120, 70, 20), playerName);
        }
        else
        {
            if (isServer)
            {
                GUI.Label(new Rect(5, 5, 100, 20), Network.player.ipAddress);
            }

            if (!gameOverShown && gameRunning && gameOver)
            {
                gameOverShown = true;
                print("trying to show game over message");
                MessageBox.Show(GameOverCallback, gameOverMessage, "Game Over", MessageBoxButtons.OK, MessageBoxIcon.None);
            }

        }
    }

    public void GameOverCallback(DialogResult result)
    {
        print("returning to main menu");
        ReturnToMainMenu();
    }

    #endregion
    
    #region Dungeon/Level methods

    Vector3 SpawnPointToGameLocation(Vector3 spawnPoint)
    {
        Vector3 location = new Vector3(spawnPoint.x, 0.1f, -spawnPoint.y);
        location *= pieceSize;
        return location;
    }

    private void LoadDungeonObjects()
    {
        Dictionary<int, TileDescriptor> descriptors = dungeon.indexer.TileDescriptors;
        foreach (int tileIndex in descriptors.Keys)
        {
            int logicalX = tileIndex % dungeonSize;
            int logicalY = tileIndex / dungeonSize;

            float tileX = logicalX * pieceSize + (float)pieceSize / 2;
            float tileZ = -logicalY * pieceSize - (float)pieceSize / 2;

            // Fetch!
            string resourceName = descriptors[tileIndex].ToString();
            //print("loading resource " + resourceName);
            GameObject tile = Instantiate(Resources.Load("kitPieces/" + resourceName, typeof(GameObject))) as GameObject;
            DontDestroyOnLoad(tile);
            tile.transform.position = new Vector3(tileX, 0, tileZ);
            sceneObjects.Add(tile);
        }
    }

    private void GenerateDungeon(bool coopMode)
    {
        dungeon = new NetworkDungeon(dungeonSize, 4, coopMode);
        dungeon.SplitAll();
        dungeon.SplitAll();
        dungeon.SplitAll();
        dungeon.SplitAll();
        dungeon.GenerateRooms();
        dungeon.GenerateCorridors();
        dungeon.GenerateMonsters();

    }

    void OnLevelWasLoaded(int level)
    {
        if ((singlePlayer || Network.isServer) && Application.loadedLevelName == "networkingGame")
        {
            print("game is running");
            gameRunning = true;
        }
        else
        {
            gameRunning = false;
        }
    }

    public void OnGameQuit()
    {
        if (!gameRunning) return;

        Network.Disconnect();

        foreach (GameObject sceneObject in sceneObjects)
        {
            if (!sceneObject.name.StartsWith("floor_")) print("Destroying scene object " + sceneObject.name);
            GameObject.Destroy(sceneObject);
        }

        GameObject.Destroy(this.gameObject);
    }

    #endregion

    #region RPC Calls

    [RPC]
    void ReceiveLevelData(byte[] serializedLevelData, int playerIndex)
    {
        print("RPC level data received, player index " + playerIndex);
        networkView.RPC("SetPlayerName", RPCMode.Server, playerIndex, playerName);
 
        //StringBuilder sb = new StringBuilder();
        //foreach (byte b in serializedLevelData) sb.AppendFormat("{0} ", b);
        //print(sb.ToString());

        playerNumber = playerIndex;
        dungeon = new NetworkDungeon(dungeonSize, serializedLevelData);
        LoadDungeonObjects();
        Vector3 location = SpawnPointToGameLocation(dungeon.spawnPointList.spawnPoints[playerNumber]);
        Network.Instantiate(playerController, location, Quaternion.identity, 0);
        Application.LoadLevel("networkingGame");
    }

    [RPC]
    void ReceiveChatMessageExpired(string message)
    {
        print("GameController.ReceiveChatMessage: " + message);
        GameObject chatObject = GameObject.Find("ChatPlaceholder");
        if (chatObject != null)
        {
            chatObject.networkView.RPC("ReceiveChatMessage", RPCMode.All, message);
        }
    }

    [RPC]
    public void SetPlayerName(int remotePlayerIndex, string remotePlayerName)
    {
        print("GameController.SetPlayerName: Player " + remotePlayerIndex.ToString() + " name set to " + remotePlayerName);
        manager.SetPlayerName(remotePlayerIndex, remotePlayerName);
    }

    [RPC]
    public void PlayerExited(NetworkPlayer player, string playerName)
    {
        print("GameController.PlayerExited: player name is " + playerName);
        Network.RemoveRPCs(player);
        Network.DestroyPlayerObjects(player);
        GameObject chatObject = GameObject.Find("ChatPlaceholder");
        if (chatObject != null)
        {
            chatObject.networkView.RPC("ReceiveChatMessage", RPCMode.All, "2" + playerName + " has left the game.");
        }
    }

    #endregion

    #region Network Events

    #region Server

    void OnPlayerConnected(NetworkPlayer player)
    {
        playerCount++;
        GameObject[] skeletons = GameObject.FindGameObjectsWithTag("Skeleton");
        foreach (GameObject skeleton in skeletons)
        {
            skeleton.GetComponent<SkeletonControl>().Scale(playerCount);
        }

        // Decide which player slot this player goes into
        int playerIndex = manager.AddPlayer(player);
        print("NetworkDungeonController.OnPlayerConnected: Player index " + playerIndex.ToString());
        if (playerIndex < 0)
        {
            // This really shouldn't ever be called... and this isn't the best way of handling it.
            // There should be different lines of messaging, really...
            // And currently it will throw an exception because we don't have this call anymore.
            // Low-priority bug.
            networkView.RPC("ReceiveChatMessage", player, "No play slots are available.");
        }
        else
        {
            networkView.RPC("ReceiveLevelData", player, serializedDungeon, playerIndex);
        }

    }

    void OnPlayerDisconnected(NetworkPlayer player)
    {
        playerCount--;
        print("GameController.OnPlayerDisconnected");
        string playerName = manager.GetPlayerName(player);
        print("player disconnected, name is " + playerName);
        if (!manager.DropPlayer(player))
        {
            print("NetworkDungeonController.OnPlayerDisconnected: Error dropping player!");
        }
        networkView.RPC("PlayerExited", RPCMode.All, player, playerName);
    }
    
    #endregion

    #region Client

    void OnConnectedToServer()
    {
        isServer = false;
        Preserver.Instance.SetValue("chatEnabled", true);
    }

    void OnFailedToConnect(NetworkConnectionError error)
    {
        HumanizeNetworkConnectionError(error);
        showConnectErrorMessage = true;
    }

    void HumanizeNetworkConnectionError(NetworkConnectionError error)
    {
        switch (error)
        {
            case NetworkConnectionError.AlreadyConnectedToAnotherServer:
                connectErrorMessage = "You are already connected to a server.";
                break;
            case NetworkConnectionError.AlreadyConnectedToServer:
                connectErrorMessage = "You are already connected to this server.";
                break;
            case NetworkConnectionError.ConnectionBanned:
                connectErrorMessage = "You are banned from connecting to this server.";
                break;
            case NetworkConnectionError.ConnectionFailed:
                connectErrorMessage = "The connection failed. Probably a bad address.";
                break;
            case NetworkConnectionError.CreateSocketOrThreadFailure:
                connectErrorMessage = "There was an error creating a socket or a thread. The socket may already be in use.";
                break;
            case NetworkConnectionError.EmptyConnectTarget:
                connectErrorMessage = "No host data was provided.";
                break;
            case NetworkConnectionError.IncorrectParameters:
                connectErrorMessage = "Invalid hostname.";
                break;
            case NetworkConnectionError.InternalDirectConnectFailed:
                connectErrorMessage = "NAT connection failed.";
                break;
            case NetworkConnectionError.InvalidPassword:
                connectErrorMessage = "Invalid password provided.";
                break;
            case NetworkConnectionError.NATPunchthroughFailed:
                connectErrorMessage = "NAT punchthrough failed.";
                break;
            case NetworkConnectionError.NATTargetConnectionLost:
                connectErrorMessage = "NAT target was lost while connecting.";
                break;
            case NetworkConnectionError.NATTargetNotConnected:
                connectErrorMessage = "NAT target is not connected to the facilitating server.";
                break;
            case NetworkConnectionError.NoError:
                connectErrorMessage = "No error occurred, which makes it a little odd that the connection failed.";
                break;
            case NetworkConnectionError.RSAPublicKeyMismatch:
                connectErrorMessage = "RSA failure; no secure connection could be established.";
                break;
            case NetworkConnectionError.TooManyConnectedPlayers:
                connectErrorMessage = "The server is at full capacity.";
                break;
        }
    }

    #endregion

    #endregion

    internal void AddSceneObject(GameObject gameObject)
    {
        print("NetworkDungeonController.AddSceneObject: " + gameObject.name);
        sceneObjects.Add(gameObject);
    }

    public static
    GameObject GetNetworkPlayerObject(NetworkPlayer player)
    {
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");
        foreach (GameObject p in players)
        {
            if (p.networkView.owner == player) return p;
        }
        return null;
    }
}
